﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using PacManLib;

namespace PacManLib
{
    /// <summary>
    /// Static helper functions for loading assemblies and finding
    /// AI classes in them.
    /// </summary>
    class ReflectionUtils
    {
        #region Public methods

        /// <summary>
        /// Loads the assembly passed in, and parses it to find a class that 
        /// implements IPacManAI.
        public static Type getPacManAIFromAssembly(String assemblyName)
        {
            return getAIFromAssembly(assemblyName, typeof(IPacManAI));
        }

        /// <summary>
        /// Loads the assembly passed in, and parses it to find a class that 
        /// implements IGhostAI.
        public static Type getGhostAIFromAssembly(String assemblyName)
        {
            return getAIFromAssembly(assemblyName, typeof(IGhostAI));
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Finds a class in the assembly that implements the interface-type
        /// passed in. The assembly will be loaded if it has not been loaded
        /// before.
        /// </summary>
        private static Type getAIFromAssembly(String assemblyName, Type aiInterfaceType)
        {
            // We load the assembly...
            Assembly assembly = ReflectionUtils.loadAssembly(assemblyName);

            // We look through the types it contains, finding the one
            // that implements the interface-type passed in...
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                if (aiInterfaceType.IsAssignableFrom(type)  
                    && 
                    type.IsAbstract == false)
                {
                    return type;
                }
            }
            return null;
        }

        /// <summary>
        /// Loads the assembly requested. We try loading it from the same location
        /// as the calling assembly and also from the current working folder.
        /// </summary>
        private static Assembly loadAssembly(string assemblyName)
        {
            assemblyName = assemblyName.Trim();
            if (assemblyName.Substring(0, 2) == ".\\")
            {
                assemblyName = assemblyName.Substring(2);
            }

            // We check if we have already loaded the assembly...
            if (m_mapNamesToAssemblies.ContainsKey(assemblyName))
            {
                return m_mapNamesToAssemblies[assemblyName];
            }

            // We first try loading the requested assembly from the folder containing
            // the calling assembly. If this fails, we try loading it from the current
            // working folder.
            // We do this because we may be loading the assembly from - for example - 
            // an Excel add-in, and the working folder may not be where the calling 
            // assembly lives...
            Assembly result;
            try
            {
                // We try loading it from the current assembly's folder...
                String strCallingAssemblyFilename = Assembly.GetExecutingAssembly().Location;
                int iIndex = strCallingAssemblyFilename.LastIndexOf("\\");
                String strCallingAssemblyFolder = strCallingAssemblyFilename.Substring(0, iIndex);
                String strAssemblyPath = Path.Combine(strCallingAssemblyFolder, assemblyName);
                result = Assembly.LoadFrom(strAssemblyPath);
            }
            catch (Exception)
            {
                // The assembly was not in the same folder as the current running
                // assembly, so we try loading it from the current working folder...
                String strAssemblyPath = Path.Combine(Directory.GetCurrentDirectory(), assemblyName);
                result = Assembly.LoadFrom(strAssemblyPath);
            }

            // We add it to our collection...
            m_mapNamesToAssemblies.Add(assemblyName, result);

            return result;
        }

        #endregion

        #region Private data

        // The collection of assemblies we've loaded...
        private static Dictionary<string, Assembly> m_mapNamesToAssemblies = new Dictionary<string, Assembly>();

        #endregion
    }
}
